/*-------------------------------------------------------------------------
 *
 * session.c
 *		用户会话的封装。
 *
 * 这旨在包含需要在为客户端会话执行工作的后端之间共享的数据。
 * 特别是这样的会话在并行查询的领导者和工作进程之间共享。
 * 在某个后续时间点，它可能还会成为将后端与客户端连接分开的有用基础设施，
 * 例如用于池化的目的。
 *
 * 目前，这个基础设施用于共享：
 * - 瞬态行类型的 typemod 注册表，即 BlessTupleDesc 等。
 *
 * Portions Copyright (c) 2017-2022, PostgreSQL Global Development Group
 *
 * src/backend/access/common/session.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/session.h"
#include "storage/lwlock.h"
#include "storage/shm_toc.h"
#include "utils/memutils.h"
#include "utils/typcache.h"

/* 会话级 DSM TOC 的魔数。 */
#define SESSION_MAGIC						0xabb0fbc9

/*
 * 我们希望创建一个 DSA 区域来存储与会话具有相同
 * 生命周期的共享状态。到目前为止，它仅用于保存共享记录
 * 类型注册表。我们不希望它在常见情况下创建任何 DSM 段，
 * 所以我们会给它足够的空间来容纳一个非常小的
 * SharedRecordTypmodRegistry。
 */
#define SESSION_DSA_SIZE					0x30000

/*
 * 会话级 DSM 区域中状态共享的魔数。
 */
#define SESSION_KEY_DSA						UINT64CONST(0xFFFFFFFFFFFF0001)
#define SESSION_KEY_RECORD_TYPMOD_REGISTRY	UINT64CONST(0xFFFFFFFFFFFF0002)

/* 该后端的当前会话。 */
Session    *CurrentSession = NULL;

/*
 * 设置 CurrentSession 以指向一个空 Session 对象。
 */
void InitializeSession(void)
{
	CurrentSession = MemoryContextAllocZero(TopMemoryContext, sizeof(Session));
}

/*
 * 如果会话级 DSM 段尚未初始化，则初始化它，并
 * 返回其句柄，以便工作进程可以附加到它。
 *
 * 与上下文级 DSM 段不同，此段及其内容将
 * 被重用以供将来的并行查询使用。
 *
 * 如果由于资源不足而无法分配段，则返回 DSM_HANDLE_INVALID。
 */
dsm_handle
GetSessionDsmHandle(void)
{
	shm_toc_estimator fc_estimator;
	shm_toc    *fc_toc;
	dsm_segment *fc_seg;
	size_t		fc_typmod_registry_size;
	size_t		fc_size;
	void	   *fc_dsa_space;
	void	   *fc_typmod_registry_space;
	dsa_area   *fc_dsa;
	MemoryContext fc_old_context;

	/*
	 * 如果我们已经在此后端创建了一个会话范围的 DSM 段，
	 * 则返回其句柄。该段将在此后端的其余生命周期中使用。
	 */
	if (CurrentSession->segment != NULL)
		return dsm_segment_handle(CurrentSession->segment);

	/* 否则，准备设置一个。 */
	fc_old_context = MemoryContextSwitchTo(TopMemoryContext);
	shm_toc_initialize_estimator(&fc_estimator);

	/* 估算会话级 DSA 区域的空间。 */
	shm_toc_estimate_keys(&fc_estimator, 1);
	shm_toc_estimate_chunk(&fc_estimator, SESSION_DSA_SIZE);

	/* 估算会话级记录 typmod 注册表的空间。 */
	fc_typmod_registry_size = SharedRecordTypmodRegistryEstimate();
	shm_toc_estimate_keys(&fc_estimator, 1);
	shm_toc_estimate_chunk(&fc_estimator, fc_typmod_registry_size);

	/* 设置段和 TOC。 */
	fc_size = shm_toc_estimate(&fc_estimator);
	fc_seg = dsm_create(fc_size, DSM_CREATE_NULL_IF_MAXSEGMENTS);
	if (fc_seg == NULL)
	{
		MemoryContextSwitchTo(fc_old_context);

		return DSM_HANDLE_INVALID;
	}
	fc_toc = shm_toc_create(SESSION_MAGIC,
						 dsm_segment_address(fc_seg),
						 fc_size);

	/* 创建会话级 DSA 区域。 */
	fc_dsa_space = shm_toc_allocate(fc_toc, SESSION_DSA_SIZE);
	fc_dsa = dsa_create_in_place(fc_dsa_space,
							  SESSION_DSA_SIZE,
							  LWTRANCHE_PER_SESSION_DSA,
							  fc_seg);
	shm_toc_insert(fc_toc, SESSION_KEY_DSA, fc_dsa_space);


	/* 创建会话范围的共享记录 typmod 注册表。 */
	fc_typmod_registry_space = shm_toc_allocate(fc_toc, fc_typmod_registry_size);
	SharedRecordTypmodRegistryInit((SharedRecordTypmodRegistry *)
								   fc_typmod_registry_space, fc_seg, fc_dsa);
	shm_toc_insert(fc_toc, SESSION_KEY_RECORD_TYPMOD_REGISTRY,
				   fc_typmod_registry_space);

	/*
	 * 如果我们能走到这一步，就可以固定共享内存，
	 * 使其在此后端的生命周期内保持映射。如果我们未能走到这一步，
	 * 我们在上面安装的任何东西（即当前的
	 * SharedRecordTypmodRegistry）的清理回调将在
	 * CurrentResourceOwner 分离 DSM 段时运行，
	 * 因此我们不会留下一个破损的 CurrentSession。
	 */
	dsm_pin_mapping(fc_seg);
	dsa_pin_mapping(fc_dsa);

	/* 通过 CurrentSession 使段和区域可用。 */
	CurrentSession->segment = fc_seg;
	CurrentSession->area = fc_dsa;

	MemoryContextSwitchTo(fc_old_context);

	return dsm_segment_handle(fc_seg);
}

/*
 * 附加到并行领导提供的会话级 DSM 段。
 */
void AttachSession(dsm_handle fc_handle)
{
	dsm_segment *fc_seg;
	shm_toc    *fc_toc;
	void	   *fc_dsa_space;
	void	   *fc_typmod_registry_space;
	dsa_area   *fc_dsa;
	MemoryContext fc_old_context;

	fc_old_context = MemoryContextSwitchTo(TopMemoryContext);

	/* 附加到 DSM 段。 */
	fc_seg = dsm_attach(fc_handle);
	if (fc_seg == NULL)
		elog(ERROR, "could not attach to per-session DSM segment");
	fc_toc = shm_toc_attach(SESSION_MAGIC, dsm_segment_address(fc_seg));

	/* 附加到 DSA 区域。 */
	fc_dsa_space = shm_toc_lookup(fc_toc, SESSION_KEY_DSA, false);
	fc_dsa = dsa_attach_in_place(fc_dsa_space, fc_seg);

	/* 通过当前会话使它们可用。 */
	CurrentSession->segment = fc_seg;
	CurrentSession->area = fc_dsa;

	/* 附加到共享记录 typmod 注册表。 */
	fc_typmod_registry_space =
		shm_toc_lookup(fc_toc, SESSION_KEY_RECORD_TYPMOD_REGISTRY, false);
	SharedRecordTypmodRegistryAttach((SharedRecordTypmodRegistry *)
									 fc_typmod_registry_space);

	/* 保持附加直到后端结束或 DetachSession()。 */
	dsm_pin_mapping(fc_seg);
	dsa_pin_mapping(fc_dsa);

	MemoryContextSwitchTo(fc_old_context);
}

/*
 * 从当前会话 DSM 段分离。显式执行此操作并非严格必要，
 * 因为我们将在后端退出时自动分离，但如果我们
 * 重用并行工作进程，则对于工作进程在附加到
 * 另一个会话之前从一个会话分离将变得重要。
 * 注意，这将运行分离钩子。
 */
void DetachSession(void)
{
	/* 运行分离钩子。 */
	dsm_detach(CurrentSession->segment);
	CurrentSession->segment = NULL;
	dsa_detach(CurrentSession->area);
	CurrentSession->area = NULL;
}
